12-5 Controller控制器的参数解析装饰器(@Param、@Body、@Query )
1. HTTP请求参数类型
1.1 常见参数类型
前端发起HTTP请求时,通常会包含以下四种核心参数类型,每种参数类型适用于不同的场景:
- 路径参数(Path Parameters)
- 定义:嵌入在URL路径中的动态变量,通常用于标识资源。
- 示例:
/users/:id
中的:id
是一个路径参数,表示用户的唯一标识符。 - 特点:
- 是URL的一部分,不可省略。
- 通常用于RESTful API的资源定位(如获取、更新或删除特定资源)。
- 查询参数(Query Parameters)
- 定义:附加在URL问号(
?
)后的键值对,用于过滤、排序或分页数据。 - 示例:
/api/users?page=1&size=10
中的page
和size
是查询参数。 - 特点:
- 可选性强,不影响资源定位。
- 常用于GET请求,传递非敏感数据。
- 定义:附加在URL问号(
- 请求体参数(Body Parameters)
- 定义:通过请求体(Body)传递的数据,通常用于POST、PUT等请求。
- 示例:在POST请求中发送JSON数据,如
{ "name": "Tom", "age": 25 }
。 - 特点:
- 支持复杂数据结构(如嵌套对象、数组)。
- 适合传递大量或敏感数据(如密码、表单数据)。
- 请求头参数(Header Parameters)
- 定义:通过HTTP请求头(Headers)传递的键值对,用于传递元数据或认证信息。
- 示例:
Authorization: Bearer token123
或X-Tenant-ID: 1001
。 - 特点:
- 常用于身份验证(如JWT)、跨域请求(CORS)或多租户系统。
- 对客户端透明,适合传递系统级信息。
💡 提示:
- 路径参数和查询参数是URL的一部分,而请求体参数和请求头参数是独立的传输方式。
- 在实际开发中,应根据业务需求选择合适的参数传递方式。
1.2 参数传递示例
以下是通过不同HTTP方法和参数类型发起请求的代码示例:
1. 查询参数(GET请求)
axios.get('/api/users?page=1&size=10')
.then(response => console.log(response.data));
typescript
- 用途:获取分页数据。
- 解析:后端通过
@Query()
装饰器获取page
和size
参数。
2. 路径参数 + 请求体(POST请求)
axios.post('/api/users/123', { name: 'Tom', age: 25 })
.then(response => console.log(response.data));
typescript
- 用途:更新ID为123的用户信息。
- 解析:
- 路径参数
123
通过@Param('id')
获取。 - 请求体数据通过
@Body()
获取。
- 路径参数
3. 请求头参数(DELETE请求)
axios.delete('/api/users', {
headers: { 'X-Tenant-ID': '1001' }
}).then(response => console.log(response.data));
typescript
- 用途:删除特定租户(Tenant)下的用户数据。
- 解析:后端通过
@Headers('X-Tenant-ID')
获取租户ID。
4. 综合示例(PUT请求)
axios.put('/api/products/456?category=electronics',
{ price: 999 },
{ headers: { 'Authorization': 'Bearer token123' } }
).then(response => console.log(response.data));
typescript
- 用途:更新电子产品分类下的商品价格。
- 解析:
- 路径参数
456
(商品ID)。 - 查询参数
category=electronics
(分类过滤)。 - 请求体
{ price: 999 }
(更新数据)。 - 请求头
Authorization
(身份验证)。
- 路径参数
💡 提示:
- 在实际开发中,建议使用TypeScript的接口(Interface)或类(Class)定义请求体和响应数据的结构,以提高代码可读性和类型安全性。
- 对于敏感数据(如密码),务必使用HTTPS协议加密传输。
1.3 参数传递的注意事项
- 路径参数:
- 必须与路由定义匹配,否则会引发404错误。
- 适合传递资源标识符(如ID)。
- 查询参数:
- 长度有限制(因浏览器和服务器而异)。
- 避免传递敏感信息(如密码),因为URL可能被日志记录或缓存。
- 请求体参数:
- 仅适用于POST、PUT、PATCH等方法。
- 支持多种数据格式(如JSON、FormData)。
- 请求头参数:
- 适合传递系统级信息(如认证令牌、语言偏好)。
- 可通过浏览器开发者工具查看,因此不适合传递敏感业务数据。
1.4 扩展知识:RESTful API设计规范
- 路径参数:用于唯一标识资源(如
/users/:id
)。 - 查询参数:用于过滤、排序或分页(如
/users?role=admin
)。 - 请求体:用于创建或更新资源(如POST
/users
)。 - 请求头:用于传递上下文信息(如认证、缓存控制)。
💡 提示:遵循RESTful规范可以提高API的可读性和一致性。
2. NestJS参数解析装饰器
2.1 核心装饰器
2.1.1 @Param()
功能:提取URL路径中的动态参数
典型用法:
@Get('users/:id')
getUser(@Param('id') userId: string) {
// 将字符串参数转为数字
const id = parseInt(userId);
return `User ID: ${id}`;
}
typescript
高级用法:
- 获取所有路径参数:
@Get('projects/:projectId/tasks/:taskId') getTask(@Param() params: { projectId: string, taskId: string }) { return `Project: ${params.projectId}, Task: ${params.taskId}`; }
typescript
最佳实践:
- 总是验证参数有效性(如使用class-validator)
- 对于数字ID,推荐使用
ParseIntPipe
管道自动转换:@Get('users/:id') getUser(@Param('id', ParseIntPipe) id: number) { // id已经是数字类型 }
typescript
2.1.2 @Query()
功能:解析URL查询字符串
典型用法:
@Get('search')
search(@Query('keyword') keyword: string) {
return `Searching for: ${keyword}`;
}
typescript
高级特性:
- 获取全部查询参数:
@Get('filter') filter(@Query() query: { page: string, size: string }) { return `Page: ${query.page}, Size: ${query.size}`; }
typescript
注意事项:
- 查询参数总是字符串类型
- 推荐为可选参数设置默认值:
@Get('list') list(@Query('page') page = '1') { // 当page参数缺失时默认为'1' }
typescript
2.1.3 @Body()
功能:解析请求体数据
标准用法:
@Post('users')
createUser(@Body() userData: any) {
return `Created user: ${userData.name}`;
}
typescript
推荐实践:
- 使用DTO(Data Transfer Object)定义数据结构:
class CreateUserDto { @IsString() name: string; @IsEmail() email: string; } @Post('users') createUser(@Body() userData: CreateUserDto) { // 自动验证数据类型 }
typescript - 结合ValidationPipe实现自动验证:
@Post('users') createUser(@Body(ValidationPipe) userData: CreateUserDto) { // 数据无效时会自动返回400错误 }
typescript
2.1.4 @Headers()
功能:读取特定请求头
典型用法:
@Get('profile')
getProfile(@Headers('authorization') auth: string) {
return `Auth token: ${auth}`;
}
typescript
高级技巧:
- 获取全部请求头:
@Get('headers') showHeaders(@Headers() headers: Record<string, string>) { return headers; }
typescript
安全提示:
- 敏感头信息(如Authorization)应通过环境变量配置
- 推荐使用专用装饰器如
@ApiBearerAuth()
(配合Swagger)
2.2 其他实用装饰器
装饰器 | 功能说明 | 典型应用场景 | 示例代码片段 |
---|---|---|---|
@Request() | 获取完整请求对象 | 需要访问底层请求细节时 | @Get() findAll(@Req() req: Request) { console.log(req.cookies); } |
@Response() | 获取响应对象 | 需要手动控制响应时 | @Get() download(@Res() res: Response) { res.download('file.pdf'); } |
@Next() | 调用下一个中间件 | 中间件链式处理 | @Get() middleware(@Next() next: NextFunction) { next(); } |
@Ip() | 获取客户端IP地址 | 安全审计/限流 | @Get() logIp(@Ip() ip: string) { logger.log(ip); } |
@HostParam() | 获取虚拟主机参数 | 多租户系统 | @Get() tenant(@HostParam('tenant') tenant: string) { ... } |
@Session() | 访问会话对象 | 需要会话状态的应用 | @Get() cart(@Session() session: Record<string, any>) { ... } |
@UploadedFile() | 处理文件上传 | 文件上传接口 | @Post('upload') upload(@UploadedFile() file: Express.Multer.File) { ... } |
2.3 装饰器组合使用示例
场景:创建一个需要认证的文件上传接口
@Post('upload')
@UseGuards(AuthGuard)
uploadFile(
@Headers('authorization') token: string,
@UploadedFile() file: Express.Multer.File,
@Body() metadata: FileMetadataDto,
@Ip() clientIp: string
) {
return {
status: 'success',
filename: file.originalname,
uploadedBy: extractUserFromToken(token),
clientIp
};
}
typescript
2.4 性能优化建议
- 装饰器选择:
- 优先使用专用装饰器(如
@Body()
而非@Request().body
) - 避免在频繁调用的路由中使用
@Request()
/@Response()
- 优先使用专用装饰器(如
- 数据转换:
- 使用内置管道(如
ParseIntPipe
)替代手动类型转换 - 对于复杂转换,创建自定义管道
- 使用内置管道(如
- 验证时机:
- 在控制器方法顶部进行参数验证
- 使用全局管道减少重复代码
扩展阅读:
3. 综合实战演示
3.1 创建多参数接口(增强版)
import {
Body,
Controller,
Headers,
Param,
Post,
Query,
ParseIntPipe,
ValidationPipe,
UsePipes
} from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('api/v1')
export class AppController {
@Post(':id')
@UsePipes(new ValidationPipe({ transform: true })) // 启用自动转换
async postHello(
@Param('id', ParseIntPipe) id: number, // 自动转换为数字
@Query('page') page = '1', // 默认值
@Body() bodyData: CreateUserDto, // 使用DTO类
@Headers('x-tenant-id') tenantId: string
) {
// 模拟业务处理
const processedData = {
...bodyData,
password: '******' // 敏感信息脱敏
};
return {
metadata: {
pathParam: id,
queryParam: page,
tenantId,
timestamp: new Date().toISOString()
},
data: processedData
};
}
}
typescript
关键改进:
- 使用
ParseIntPipe
自动转换路径参数类型 - 为查询参数添加默认值
- 引入DTO类进行类型校验(需配合class-validator)
- 响应数据标准化(metadata+data结构)
- 敏感信息自动脱敏处理
3.2 测试请求示例(完整流程)
测试准备
- 安装测试工具:
npm install -D supertest @types/supertest
bash - 创建测试文件
app.e2e-spec.ts
:
import * as request from 'supertest';
import { Test } from '@nestjs/testing';
import { AppModule } from '../src/app.module';
import { INestApplication } from '@nestjs/common';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/POST api/v1/1000', () => {
return request(app.getHttpServer())
.post('/api/v1/1000?page=1')
.set('x-tenant-id', '100001')
.send({ username: 'Tom', password: '123456' })
.expect(201)
.expect({
metadata: {
pathParam: 1000,
queryParam: '1',
tenantId: '100001',
timestamp: expect.any(String)
},
data: {
username: 'Tom',
password: '******'
}
});
});
afterAll(async () => {
await app.close();
});
});
typescript
测试用例矩阵
测试场景 | 请求参数 | 预期结果 |
---|---|---|
正常请求 | 所有参数完整 | 返回200+完整响应 |
缺失查询参数 | 不传page | 使用默认值page=1 |
无效路径参数 | id=abc | 返回400错误 |
缺失请求头 | 不传x-tenant-id | 返回401未授权 |
无效请求体 | password少于6位 | 返回400验证错误 |
3.3 调试建议(增强版)
高级调试技巧:
- 请求追踪:
import { Request } from 'express'; @Post() debug(@Req() req: Request) { console.log({ method: req.method, url: req.originalUrl, headers: req.headers, body: req.body }); }
typescript - 性能分析:
# 安装性能监控 npm install @nestjs/platform-express @sentry/node
bash - API文档生成:
npm install @nestjs/swagger
bash
配置Swagger后可通过/api
访问交互式文档
3.4 生产环境最佳实践
- 参数安全:
import { SanitizePipe } from './pipes/sanitize.pipe'; @Post() safeEndpoint(@Body(new SanitizePipe()) data: unknown) { // 自动过滤XSS等危险内容 }
typescript - 限流保护:
import { Throttle } from '@nestjs/throttler'; @Throttle(100, 60) // 60秒内最多100次请求 @Get() sensitiveEndpoint() { return '重要数据'; }
typescript - 缓存策略:
import { CacheInterceptor } from '@nestjs/cache-manager'; @UseInterceptors(CacheInterceptor) @Get(':id') cachedData(@Param('id') id: string) { return this.service.getExpensiveData(id); }
typescript
扩展阅读:
↑